module Interaction

import ..Model
const XY = Model.XY

import Gtk
import Reactive

const G = Gtk
const R = Reactive

using Lazy
using ..Helpers

function gtk_to_signal(widget,
                       event_type,
                       proc_f,
                       init :: T) where {T}
  signal = R.Signal(init)
  G.signal_connect(widget, event_type) do _, event
    @>> proc_f(event) push!(signal)
  end
  signal
end

@enum MouseButton begin
  LeftMouseButton
  OtherMouseButton
end

function MouseButton(ev :: G.GdkEventButton) :: MouseButton
  b = ev.button
  if b == 1
    LeftMouseButton
  else
    OtherMouseButton
  end
end

struct MousePress
  button :: MouseButton
end

struct MouseRelease
  button :: MouseButton
end

MouseButtonEvent = Union{MousePress, MouseRelease}

MousePress(ev :: G.GdkEventButton) = @> ev MouseButton MousePress
MouseRelease(ev :: G.GdkEventButton) = @> ev MouseButton MouseRelease

DUMMY_MOUSE_PRESS = @> OtherMouseButton MousePress
DUMMY_MOUSE_RELEASE = @> OtherMouseButton MouseRelease

function mouse_signals(widget
                      ) :: Tuple{R.Signal{XY},
                                 R.Signal{MousePress},
                                 R.Signal{MouseRelease}}
  MOUSE_MOTION_EVENT = "motion-notify-event"
  mouse_position = gtk_to_signal(widget,
                         MOUSE_MOTION_EVENT,
                         x -> XY(x),
                         zero(XY))
  MOUSE_BUTTON_PRESS_EVENT = "button-press-event"
  MOUSE_BUTTON_RELEASE_EVENT = "button-release-event"
  Masks = G.GConstants.GdkEventMask
  G.add_events(widget, Masks.BUTTON_PRESS | Masks.BUTTON_RELEASE)
  mouse_presses = gtk_to_signal(widget,
                                MOUSE_BUTTON_PRESS_EVENT,
                                MousePress,
                                DUMMY_MOUSE_PRESS)
  mouse_releases = gtk_to_signal(widget,
                                 MOUSE_BUTTON_RELEASE_EVENT,
                                 MouseRelease,
                                 DUMMY_MOUSE_RELEASE)
  (mouse_position,
   mouse_presses,
   mouse_releases)
end

function is_lmb(mouse_button_event :: MouseButtonEvent)
  mouse_button_event.button == LeftMouseButton
end

const XYOrNothing = Union{XY,Nothing}

function drag_signals(mouse_position :: R.Signal{XY},
                      mouse_presses :: R.Signal{MousePress},
                      mouse_releases :: R.Signal{MouseRelease}
                     ) :: R.Signal{XYOrNothing}

  reference_pos = R.Signal(XYOrNothing, nothing)

  lmb_pressed = filter(is_lmb, mouse_presses)
  lmb_released = filter(is_lmb, mouse_releases)

  set_reference_pos!(mouse_position :: XY) =
    push!(reference_pos, mouse_position)

  unset_reference_pos!(_) =
    push!(reference_pos, nothing)

  @>> R.sampleon(lmb_pressed, mouse_position) foreach(set_reference_pos!);
  @>> lmb_released foreach(unset_reference_pos!);

  function calc_drag(reference_pos :: XYOrNothing, current_pos :: XY
                    ) :: XYOrNothing
    if reference_pos == nothing
      nothing
    else
      current_pos - reference_pos
    end
  end

 #foreach(reference_pos, mouse_position) do reference_pos, mouse_position
 #  @debug "poses" reference_pos mouse_position
 #end

  drag = R.map(calc_drag, reference_pos, mouse_position, typ= XYOrNothing)

  drag = R.droprepeats(drag)

 #foreach(drag) do drag
 #  @debug "drag" drag
 #end

  drag
end

function pan_signal(drag :: R.Signal{XYOrNothing}
                   ) :: R.Signal{XY}

  identity_pan = zero(XY)

  old_pan = R.Signal(identity_pan)

  previous_drag = R.previous(drag, identity_pan)

  function is_dragging_stop(previous_drag, drag)
    previous_some = issome(previous_drag)
    current_some = issome(drag)
    previous_some && !current_some
  end

  dragging_stop = @>> begin
    map(is_dragging_stop, previous_drag, drag)
    R.droprepeats()
    filter(isequal(true))
  end

  drag_before_stop = R.sampleon(dragging_stop, previous_drag)

 #foreach(drag_before_stop) do drag_before_stop
 #  @debug "drag_before_stop" drag_before_stop
 #end

  foreach(drag_before_stop) do drag_before_stop
    push!(old_pan, R.value(old_pan) + drag_before_stop)
  end

  new_pan = map(or(zero(XY)), drag)

 #foreach(new_pan) do new_pan
 #  @debug "new_pan" new_pan
 #end

 #foreach(old_pan) do old_pan
 #  @debug "old_pan" old_pan
 #end

  agg_pan = map(+, new_pan, old_pan)

  agg_pan
end

function pan_signal(widget)
  mouse_position, mouse_presses, mouse_releases = mouse_signals(widget)
  drag = drag_signals(mouse_position, mouse_presses, mouse_releases)
  pan = pan_signal(drag)
  pan
end

end
